﻿//Edit @ FileVersion 5.6.3.0311 :MVVM窗体 增加动态绑定事件的重载方法BindCommandControls

using System;
using System.Collections.Generic;
using System.Reflection;
using System.Windows.Forms;
using PWMIS.Common;
using PWMIS.Core;
using PWMIS.DataForms.Adapter;

namespace PWMIS.Windows.Mvvm
{
    /// <summary>
    ///     MVVM 窗体基类，如果需要在SOD Windows数据窗体上实现MVVM效果，请继承本类。
    /// </summary>
    public partial class MvvmForm : Form, IMvvmForm
    {
        /// <summary>
        ///     命令方法
        /// </summary>
        public delegate void CommandMethod();

        /// <summary>
        ///     带参数的命令方法
        /// </summary>
        /// <typeparam name="T">参数类型</typeparam>
        /// <param name="para">参数</param>
        public delegate void CommandMethod<T>(T para);


        /// <summary>
        ///     默认构造函数
        /// </summary>
        public MvvmForm()
        {
            InitializeComponent();
            //dictCommand = new Dictionary<object, Delegate>();
        }

        /// <summary>
        ///     用于解决【线程间操作无效】的问题
        /// </summary>
        /// <typeparam name="T">控件或者其它对象类型</typeparam>
        /// <param name="ctl">对象实例</param>
        /// <param name="action">要对控件或者对象执行的方法，在此方法内对控件或者绑定的数据对象进行修改</param>
        public void FormInvoke<T>(T ctl, Action<T> action)
        {
            if (InvokeRequired)
                Invoke(new MyAction(() => action(ctl)));
            else
                action(ctl);
        }

        /// <summary>
        ///     用于解决【线程间操作无效】的问题
        /// </summary>
        /// <param name="action">自定义的方法，在此方法内对控件或者绑定的数据对象进行修改</param>
        public void FormInvoke(MyAction action)
        {
            if (InvokeRequired)
                Invoke(new MyAction(() => action()));
            else
                action();
        }

        //private Dictionary<object, Delegate> dictCommand; //CommandMethod
        /// <summary>
        ///     MVVM窗体绑定处理过程发生异常的事件
        /// </summary>
        public event EventHandler MvvmBinderErrorEvent;

        private void RaiseBinderError(object sender, Exception ex)
        {
            if (MvvmBinderErrorEvent != null)
                MvvmBinderErrorEvent(sender, new MvvmBinderErrorEventArgs(ex.Message));
            else
                throw new NotSupportedException(
                    "MVVM Binder Exception,because not handler the MvvmBinderErrorEvent.see inner exception.", ex);
        }

        /// <summary>
        ///     绑定未处理的数据控件，如果有控件未处理这里将抛出异常
        /// </summary>
        /// <param name="control">数据控件</param>
        /// <param name="dataSource">数据源对象</param>
        /// <param name="dataMember">要绑定的成员名称</param>
        protected virtual void BindDataControl(IDataControl control, object dataSource, string dataMember)
        {
            throw new NotImplementedException("请重写此方法处理当前控件。注意不要再调用此基类方法。");
        }

        /// <summary>
        ///     对数据控件实现双向绑定
        /// </summary>
        /// <param name="controls">要搜索数据控件的窗体控件集合</param>
        public void BindDataControls(List<IDataControl> controls)
        {
            foreach (var control in controls)
            {
                //control.LinkObject 这里都是 "DataContext"
                var dataSource = GetInstanceByMemberName(control.LinkObject);
                if (control is TextBox)
                    ((TextBox)control).DataBindings.Add("Text", dataSource, control.LinkProperty);
                else if (control is Label)
                    ((Label)control).DataBindings.Add("Text", dataSource, control.LinkProperty);
                else if (control is ListBox)
                    ((ListBox)control).DataBindings.Add("SelectedValue", dataSource, control.LinkProperty, false,
                        DataSourceUpdateMode.OnPropertyChanged);
                else if (control is DateTimePicker)
                    ((DateTimePicker)control).DataBindings.Add("Value", dataSource, control.LinkProperty, false,
                        DataSourceUpdateMode.OnPropertyChanged);
                else
                    //自定义处理控件类型
                    BindDataControl(control, dataSource, control.LinkProperty);
            }
        }

        /// <summary>
        ///     绑定ViewModel的命令到窗体的按钮等控件上
        /// </summary>
        /// <param name="control">ButtonBase 按钮等执行命令调用的控件</param>
        /// <param name="command">要执行的命令委托方法</param>
        public void BindCommandControls(Control control, CommandMethod command)
        {
            if (control is ButtonBase)
                //dictCommand.Add(control, command);
                ((ButtonBase)control).Click += (sender, e) =>
                {
                    //((CommandMethod)dictCommand[sender])(); 
                    CommandEventMethod(sender, e, command);
                };
        }

        /// <summary>
        ///     绑定ViewModel的命令到窗体的任意控件上
        /// </summary>
        /// <param name="control">窗体控件</param>
        /// <param name="controlEvent">控件事件名称</param>
        /// <param name="command">命令方法</param>
        public void BindCommandControls(Control control, string controlEvent, CommandMethod command)
        {
            EventHandler hander = (sender, e) => { CommandEventMethod(sender, e, command); };

            var ctrType = control.GetType();
            ctrType.GetEvent(controlEvent).AddEventHandler(control, hander);
        }

        /// <summary>
        ///     绑定ViewModel的命令到窗体的任意控件的特定类型事件上
        /// </summary>
        /// <param name="control">引发事件的控件</param>
        /// <param name="controlEvent">控件的事件名</param>
        /// <param name="target">要绑定的事件处理方法所在的对象</param>
        /// <param name="EventHandleMethod">事件处理方法</param>
        public void BindCommandControls(Control control, string controlEvent, object target, string EventHandleMethod)
        {
            var ctrType = control.GetType();
            var eventInfo = ctrType.GetEvent(controlEvent);
            var dele = Delegate.CreateDelegate(eventInfo.EventHandlerType, target, EventHandleMethod);
            eventInfo.AddEventHandler(control, dele);
        }

        /// <summary>
        ///     (自动)绑定命令按钮的ControlEvent 到关联的命令对象
        /// </summary>
        /// <param name="control"></param>
        public void BindCommandControls(ICommandControl control)
        {
            var dataSource = GetInstanceByMemberName(control.CommandObject);
            var propNames = control.CommandName.Split('.');
            var obj = GetPropertyValue(dataSource, propNames);
            var command = obj as IMvvmCommand;

            BindCommandControls(control, control.ControlEvent, command);
        }

        /// <summary>
        ///     绑定控件的事件到命令接口对象
        /// </summary>
        /// <param name="control">窗体控件</param>
        /// <param name="controlEvent">控件的事件</param>
        /// <param name="command">要绑定的命令接口对象</param>
        public void BindCommandControls(object control, string controlEvent, IMvvmCommand command)
        {
            EventHandler hander = (sender, e) =>
            {
                object paraValue = null;
                if (control is ICommandControl)
                    try
                    {
                        var cmdCtr = control as ICommandControl;
                        if (cmdCtr.ParameterObject != null && cmdCtr.ParameterProperty != null)
                        {
                            var paraSource = GetInstanceByMemberName(cmdCtr.ParameterObject);
                            var paraPropNames = cmdCtr.ParameterProperty.Split('.');
                            paraValue = GetPropertyValue(paraSource, paraPropNames);
                        }
                    }
                    catch (Exception ex)
                    {
                        RaiseBinderError(control, ex);
                        return;
                    }

                if (command.BeforExecute(paraValue))
                    try
                    {
                        command.Execute(paraValue);
                    }
                    catch (Exception ex)
                    {
                        RaiseBinderError(control, ex);
                    }
                    finally
                    {
                        command.AfterExecute();
                    }
            };

            var ctrType = control.GetType();
            ctrType.GetEvent(controlEvent).AddEventHandler(control, hander);
        }

        /// <summary>
        ///     绑定一个命令控件到一个有参数的命令方法上
        /// </summary>
        /// <typeparam name="T">命令方法的参数类型</typeparam>
        /// <param name="control">命令控件</param>
        /// <param name="command">带参数的命令方法</param>
        public void BindCommandControls<T>(ICommandControl control, CommandMethod<T> command)
        {
            //dictCommand.Add(control, command);
            EventHandler hander = (sender, e) => { CommandEventMethod(sender, e, command); };

            var ctrType = control.GetType();
            ctrType.GetEvent(control.ControlEvent).AddEventHandler(control, hander);
        }

        private void CommandEventMethod(object sender, EventArgs e, CommandMethod command)
        {
            try
            {
                command();
            }
            catch (Exception ex)
            {
                RaiseBinderError(sender, ex);
            }
        }

        private void CommandEventMethod<T>(object sender, EventArgs e, CommandMethod<T> command)
        {
            var cmdCtr = sender as ICommandControl;
            try
            {
                //这里不处理命令控件关联的命令对象
                var dataSource = GetInstanceByMemberName(cmdCtr.ParameterObject);
                var paraValue = GetCommandParameterValue<T>(dataSource, cmdCtr.ParameterProperty);
                //CommandMethod<T> method = (CommandMethod<T>)dictCommand[sender];
                //method(paraValue);
                command(paraValue);
            }
            catch (Exception ex)
            {
                RaiseBinderError(sender, ex);
            }
        }


        /// <summary>
        ///     根据成员名称获取成员的实例
        /// </summary>
        /// <param name="memberName">MVVM窗体上的数据成员属性名</param>
        /// <returns></returns>
        protected virtual object GetInstanceByMemberName(string memberName)
        {
            var obj = GetType().InvokeMember(memberName,
                BindingFlags.Instance |
                BindingFlags.GetField |
                BindingFlags.GetProperty |
                BindingFlags.NonPublic |
                BindingFlags.Public,
                null, this, null);
            if (obj == null)
                throw new Exception("当前窗体未找到名称为 " + memberName + " 的实例成员，请检查数据绑定控件的 LinkObject 属性。");

            return obj;
        }

        /// <summary>
        ///     从源对象调用多层次属性对象，例如 user.Order.ID;
        /// </summary>
        /// <typeparam name="T">值的类型</typeparam>
        /// <param name="dataSource">源对象</param>
        /// <param name="propertyName">带层次的属性对象</param>
        /// <returns></returns>
        private T GetCommandParameterValue<T>(object dataSource, string propertyName)
        {
            var propNames = propertyName.Split('.');
            var obj = GetPropertyValue(dataSource, propNames);
            //处理结果转换错误
            return CommonUtil.ChangeType<T>(obj);
        }

        private object GetPropertyValue(object dataSource, string[] propNames)
        {
            var t = dataSource.GetType();
            if (propNames.Length > 1)
            {
                var fi = t.GetField(propNames[0], BindingFlags.Public | BindingFlags.Instance);
                if (fi != null)
                {
                    var fieldObj = fi.GetValue(dataSource);
                    var newPropNames = new string[propNames.Length - 1];
                    propNames.CopyTo(newPropNames, 1);
                    return GetPropertyValue(fieldObj, newPropNames);
                }

                var pi = t.GetProperty(propNames[0], BindingFlags.Public | BindingFlags.Instance);
                if (pi != null)
                {
                    var propObj = pi.GetValue(dataSource, null);
                    var newPropNames = new string[propNames.Length - 1];
                    propNames.CopyTo(newPropNames, 1);
                    return GetPropertyValue(propObj, newPropNames);
                }
            }
            else
            {
                var fi = t.GetField(propNames[0], BindingFlags.Public | BindingFlags.Instance);
                if (fi != null)
                {
                    var fieldObj = fi.GetValue(dataSource);
                    return fieldObj;
                }

                var pi = t.GetProperty(propNames[0], BindingFlags.Public | BindingFlags.Instance);
                if (pi != null)
                {
                    var propObj = pi.GetValue(dataSource, null);
                    return propObj;
                }
            }

            //统一抛出错误事件
            throw new Exception("在对象" + t.Name + " 中没有找到名为 " + propNames[0] + " 的字段或者属性！");
        }

        private void MvvmForm_Load(object sender, EventArgs e)
        {
            var ibControls = MyWinForm.GetIBControls(Controls);
            BindDataControls(ibControls);
        }
    }
}